package cn.twelvet.websocket.netty.domain;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.DatagramPacket;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.AttributeKey;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;

/**
 * @author twelvet
 */
public class NettySession {

	private final Channel channel;

	NettySession(Channel channel) {
		this.channel = channel;
	}

	/**
	 * set subprotocols on {@link cn.twelvet.websocket.netty.annotation.BeforeHandshake}
	 * @param subprotocols String
	 */
	public void setSubprotocols(String subprotocols) {
		setAttribute("subprotocols", subprotocols);
	}

	public ChannelFuture sendText(String message) {
		return channel.writeAndFlush(new TextWebSocketFrame(message));
	}

	public ChannelFuture sendText(ByteBuf byteBuf) {
		return channel.writeAndFlush(new TextWebSocketFrame(byteBuf));
	}

	public ChannelFuture sendText(ByteBuffer byteBuffer) {
		ByteBuf buffer = channel.alloc().buffer(byteBuffer.remaining());
		buffer.writeBytes(byteBuffer);
		return channel.writeAndFlush(new TextWebSocketFrame(buffer));
	}

	public ChannelFuture sendText(TextWebSocketFrame textWebSocketFrame) {
		return channel.writeAndFlush(textWebSocketFrame);
	}

	public ChannelFuture sendBinary(byte[] bytes) {
		ByteBuf buffer = channel.alloc().buffer(bytes.length);
		return channel.writeAndFlush(new BinaryWebSocketFrame(buffer.writeBytes(bytes)));
	}

	public ChannelFuture sendBinary(ByteBuf byteBuf) {
		return channel.writeAndFlush(new BinaryWebSocketFrame(byteBuf));
	}

	public ChannelFuture sendBinary(ByteBuffer byteBuffer) {
		ByteBuf buffer = channel.alloc().buffer(byteBuffer.remaining());
		buffer.writeBytes(byteBuffer);
		return channel.writeAndFlush(new BinaryWebSocketFrame(buffer));
	}

	public ChannelFuture sendBinary(BinaryWebSocketFrame binaryWebSocketFrame) {
		return channel.writeAndFlush(binaryWebSocketFrame);
	}

	public <T> void setAttribute(String name, T value) {
		AttributeKey<T> sessionIdKey = AttributeKey.valueOf(name);
		channel.attr(sessionIdKey).set(value);
	}

	public <T> T getAttribute(String name) {
		AttributeKey<T> sessionIdKey = AttributeKey.valueOf(name);
		return channel.attr(sessionIdKey).get();
	}

	public Channel channel() {
		return channel;
	}

	/**
	 * Returns the globally unique identifier of this {@link Channel}.
	 * @return ChannelId
	 */
	public ChannelId id() {
		return channel.id();
	}

	/**
	 * Returns the configuration of this channel.
	 * @return ChannelConfig
	 */
	public ChannelConfig config() {
		return channel.config();
	}

	/**
	 * Returns {@code true} if the {@link Channel} is open and may get active later
	 * @return boolean
	 */
	public boolean isOpen() {
		return channel.isOpen();
	}

	/**
	 * Returns {@code true} if the {@link Channel} is registered with an
	 * {@link EventLoop}.
	 * @return boolean
	 */
	public boolean isRegistered() {
		return channel.isRegistered();
	}

	/**
	 * Return {@code true} if the {@link Channel} is active and so connected.
	 * @return boolean
	 */
	public boolean isActive() {
		return channel.isActive();
	}

	/**
	 * Return the {@link ChannelMetadata} of the {@link Channel} which describe the nature
	 * of the {@link Channel}.
	 * @return ChannelMetadata
	 */
	public ChannelMetadata metadata() {
		return channel.metadata();
	}

	/**
	 * Returns the local address where this channel is bound to. The returned
	 * {@link SocketAddress} is supposed to be down-cast into more concrete type such as
	 * {@link InetSocketAddress} to retrieve the detailed information.
	 * @return the local address of this channel. {@code null} if this channel is not
	 * bound.
	 */
	public SocketAddress localAddress() {
		return channel.localAddress();
	}

	/**
	 * Returns the remote address where this channel is connected to. The returned
	 * {@link SocketAddress} is supposed to be down-cast into more concrete type such as
	 * {@link InetSocketAddress} to retrieve the detailed information.
	 * @return the remote address of this channel. {@code null} if this channel is not
	 * connected. If this channel is not connected but it can receive messages from
	 * arbitrary remote addresses (e.g. {@link DatagramChannel}, use
	 * {@link DatagramPacket#recipient()} to determine the origination of the received
	 * message as this method will return {@code null}.
	 */
	public SocketAddress remoteAddress() {
		return channel.remoteAddress();
	}

	/**
	 * Returns the {@link ChannelFuture} which will be notified when this channel is
	 * closed. This method always returns the same future instance.
	 * @return SocketAddress
	 */
	public ChannelFuture closeFuture() {
		return channel.closeFuture();
	}

	/**
	 * Returns {@code true} if and only if the I/O thread will perform the requested write
	 * operation immediately. Any write requests made when this method returns
	 * {@code false} are queued until the I/O thread is ready to process the queued write
	 * requests.
	 * @return boolean
	 */
	public boolean isWritable() {
		return channel.isWritable();
	}

	/**
	 * Get how many bytes can be written until {@link #isWritable()} returns
	 * {@code false}. This quantity will always be non-negative. If {@link #isWritable()}
	 * is {@code false} then 0.
	 * @return long
	 */
	public long bytesBeforeUnwritable() {
		return channel.bytesBeforeUnwritable();
	}

	/**
	 * Get how many bytes must be drained from underlying buffers until
	 * {@link #isWritable()} returns {@code true}. This quantity will always be
	 * non-negative. If {@link #isWritable()} is {@code true} then 0.
	 * @return long
	 */
	public long bytesBeforeWritable() {
		return channel.bytesBeforeWritable();
	}

	/**
	 * Returns an <em>internal-use-only</em> object that provides unsafe operations.
	 * @return Channel.Unsafe
	 */
	public Channel.Unsafe unsafe() {
		return channel.unsafe();
	}

	/**
	 * Return the assigned {@link ChannelPipeline}.
	 * @return ChannelPipeline
	 */
	public ChannelPipeline pipeline() {
		return channel.pipeline();
	}

	/**
	 * Return the assigned {@link ByteBufAllocator} which will be used to allocate
	 * {@link ByteBuf}s.
	 * @return ByteBufAllocator
	 */
	public ByteBufAllocator alloc() {
		return channel.alloc();
	}

	public Channel read() {
		return channel.read();
	}

	public Channel flush() {
		return channel.flush();
	}

	public ChannelFuture close() {
		return channel.close();
	}

	public ChannelFuture close(ChannelPromise promise) {
		return channel.close(promise);
	}

}
